﻿using System;
using System.Collections.Generic;
using System.Linq;
using log4net;

namespace Reactor.Messaging
{
    public class Subscriber
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="Subscriber"/> class.
        /// </summary>
        public Subscriber()
        {
            Mappings = new List<ChannelMessagesMapping>();
        }

        /// <summary>
        /// Gets or sets the name of the subscriber.
        /// </summary>
        /// <value>The name.</value>
        public string Name { get; set; }

        /// <summary>
        /// Gets or sets the channel mappings.
        /// </summary>
        /// <value>The mappings.</value>
        public IList<ChannelMessagesMapping> Mappings { get; set; }

        #region Nested Types

        public class ChannelMessagesMapping
        {
            #region Fields

            private static readonly ILog Log = LogManager.GetLogger(typeof(ChannelMessagesMapping));
            private List<Type> _messageTypes;

            #endregion

            /// <summary>
            /// Initializes a new instance of the <see cref="ChannelMessagesMapping"/> class.
            /// </summary>
            public ChannelMessagesMapping()
            {
                MessageTypeNames = new List<string>();
            }

            /// <summary>
            /// Gets or sets the message type names. 
            /// </summary>
            /// <value>The message type names.</value>
            /// <remarks>Type names must be assembly qualified!</remarks>
            public IList<string> MessageTypeNames { get; set; }

            /// <summary>
            /// Gets or sets the MessagingChannel to which message types in this mapping should be sent.
            /// </summary>
            /// <value>The MessagingChannel.</value>
            public IMessagingChannel MessagingChannel { get; set; }

            /// <summary>
            /// Converts values found in MessageTypeNames property to actual System.Type instances.
            /// </summary>
            /// <returns>Message types</returns>
            public virtual Type[] GetMessageTypes()
            {
                // Watch for empty MessageTypeNames list
                if(MessageTypeNames == null || MessageTypeNames.Count == 0)
                {
                    _messageTypes = new List<Type>();
                    return _messageTypes.ToArray();
                }

                // Watch for existing types array
                if (_messageTypes != null && _messageTypes.Count == MessageTypeNames.Count)
                    return _messageTypes.ToArray();

                // Need to create types array for future requests
                var list = new List<Type>(MessageTypeNames.Count);
                foreach (var messageTypeName in MessageTypeNames)
                {
                    var type = Type.GetType(messageTypeName);

                    if(type != null)
                        list.Add(type);
                    else
                    {
                        if (Log != null && Log.IsWarnEnabled)
                            Log.WarnFormat("Unable to resolve message type: {0}.", messageTypeName);
                    }
                }

                _messageTypes = list;
                return _messageTypes.ToArray();
            }

            public virtual void AddMessageType(Type messageType)
            {
                // Make sure we've already created a strongly typed private collection of types from the original serialized string
                GetMessageTypes();

                if(_messageTypes.FirstOrDefault(t => t == messageType) == null)
                {
                    _messageTypes.Add(messageType);

                    var typeName = messageType.AssemblyQualifiedName;
                    if(!MessageTypeNames.Contains(typeName))
                        MessageTypeNames.Add(typeName);
                }
            }

            public virtual void AddMessageTypes(IEnumerable<Type> messageTypes)
            {
                foreach (var messageType in messageTypes)
                    AddMessageType(messageType);
            }

            public virtual void RemoveMessageType(Type messageType)
            {
                // Make sure we've already created a strongly typed private collection of types from the original serialized string
                GetMessageTypes();

                _messageTypes.Remove(messageType);

                var typeName = messageType.AssemblyQualifiedName;
                MessageTypeNames.Remove(typeName);
            }
        }

        #endregion
    }
}
